from typing import Any

from litestar import Litestar, asgi, delete, get, patch, post
from litestar.contrib.jinja import JinjaTemplateEngine
from litestar.exceptions import NotFoundException
from litestar.static_files import StaticFilesConfig
from litestar.template import TemplateConfig
from litestar.types import Receive, Scope, Send
from piccolo.engine import engine_finder
from piccolo_admin.endpoints import create_admin
from pydantic import BaseModel

from home.endpoints import home
from home.piccolo_app import APP_CONFIG
from home.tables import Task

"""
NOTE: `create_pydantic_model` is not compatible with Litestar
version higher than 2.11.0. If you are using Litestar<=2.11.0, 
you can use `create_pydantic_model` as in other asgi templates

from piccolo.utils.pydantic import create_pydantic_model

TaskModelIn: Any = create_pydantic_model(
    table=Task,
    model_name="TaskModelIn",
)
TaskModelOut: Any = create_pydantic_model(
    table=Task,
    include_default_columns=True,
    model_name="TaskModelOut",
)
"""


class TaskModelIn(BaseModel):
    name: str
    completed: bool = False


class TaskModelOut(BaseModel):
    id: int
    name: str
    completed: bool = False


# mounting Piccolo Admin
@asgi("/admin/", is_mount=True)
async def admin(scope: "Scope", receive: "Receive", send: "Send") -> None:
    await create_admin(tables=APP_CONFIG.table_classes)(scope, receive, send)


# Check if the record is None. Use for query callback
def check_record_not_found(result: dict[str, Any]) -> dict[str, Any]:
    if result is None:
        raise NotFoundException(detail="Record not found")
    return result


@get("/tasks", tags=["Task"])
async def tasks() -> list[TaskModelOut]:
    tasks = await Task.select().order_by(Task._meta.primary_key, ascending=False)
    return [TaskModelOut(**task) for task in tasks]


@get("/tasks/{task_id:int}", tags=["Task"])
async def single_task(task_id: int) -> TaskModelOut:
    task = (
        await Task.select()
        .where(Task._meta.primary_key == task_id)
        .first()
        .callback(check_record_not_found)
    )
    return TaskModelOut(**task)


@post("/tasks", tags=["Task"])
async def create_task(data: TaskModelIn) -> TaskModelOut:
    task = Task(**data.model_dump())
    await task.save()
    return TaskModelOut(**task.to_dict())


@patch("/tasks/{task_id:int}", tags=["Task"])
async def update_task(task_id: int, data: TaskModelIn) -> TaskModelOut:
    task = (
        await Task.objects()
        .get(Task._meta.primary_key == task_id)
        .callback(check_record_not_found)
    )

    for key, value in data.model_dump().items():
        setattr(task, key, value)

    await task.save()
    return TaskModelOut(**task.to_dict())


@delete("/tasks/{task_id:int}", tags=["Task"])
async def delete_task(task_id: int) -> None:
    task = (
        await Task.objects()
        .get(Task._meta.primary_key == task_id)
        .callback(check_record_not_found)
    )
    await task.remove()


async def open_database_connection_pool():
    try:
        engine = engine_finder()
        await engine.start_connection_pool()
    except Exception:
        print("Unable to connect to the database")


async def close_database_connection_pool():
    try:
        engine = engine_finder()
        await engine.close_connection_pool()
    except Exception:
        print("Unable to connect to the database")


app = Litestar(
    route_handlers=[
        admin,
        home,
        tasks,
        single_task,
        create_task,
        update_task,
        delete_task,
    ],
    template_config=TemplateConfig(
        directory="home/templates", engine=JinjaTemplateEngine
    ),
    static_files_config=[
        StaticFilesConfig(directories=["static"], path="/static/"),
    ],
    on_startup=[open_database_connection_pool],
    on_shutdown=[close_database_connection_pool],
)
